/*_____________________________________________________________________________
||
|| DesignWare APB UART Baremetal Driver
||
|| Jinfeng Zhuang, 2024.Week17
||
|| Feature:
||   - Provide only the wrapper of registers
||   - No send/recv related APIs provided
||
|| Experience:
||   - Verified on LS302
||   - If input clock is slow, the baudrate not be precision,
||     check manually or use DLF register
||   - If FIFO not work, test it individual
||   - If continuously block recv not correct, open FIFO may fix it
||   - If FIFO enabled, PC send something, but recv twice with delay, verified
||_____________________________________________________________________________
*/

//_____________________________________
//
// Implementation
//_____________________________________
//

typedef unsigned int          U32;
typedef unsigned char         U8;
typedef volatile unsigned int VU32;

#define TRUE (1)
#define FALSE (0)

struct dw_uart_reg
{
    union
    {
        VU32 DR;
        VU32 DLL;
    } DR;

    union
    {
        VU32 IER;
        VU32 DLH;
    } IER;

    union
    {
        VU32 FCR;
        VU32 IIR;
    } FCR;

    VU32 LCR;
    VU32 MCR;
    VU32 LSR;
    VU32 MSR;
    VU32 SCR;
    VU32 LPDLL;
    VU32 LPDLH;
    VU32 RSVD_0x28[2];
    VU32 SRBR[16];
    VU32 STHR[16];
    VU32 FAR;
    VU32 TFR;
    VU32 RFW;
    VU32 USR;
    VU32 TFL;
    VU32 RFL;
};

#define UART_FCR_ENABLE    (1 << 0)
#define UART_FAR_EN        (1 << 0)

/*
 * Interrupt Related
 */
#define UART_IER_RX        (1 << 0)
#define UART_IER_TX        (1 << 1)

#define UART_LCR_DLAB      (1 << 7)
#define UART_LCR_NO_PARITY (0 << 3)   // 0: Disable Parity
#define UART_LCR_1BIT_STOP (0 << 2)   // 0: 1 stop bit
#define UART_LCR_8BIT_DATA (3 << 0)   // 3: 8 data bits

#define UART_LSR_THRE      (1 << 5)
#define UART_LSR_RXNE      (1 << 0)

void hal_uart_int_tx_enable(U32 base, U32 enable)
{
    struct dw_uart_reg* regs = (struct dw_uart_reg*)base;

    if (enable)
    {
        regs->IER.IER |= UART_IER_TX;
    }
    else
    {
        regs->IER.IER &= ~UART_IER_TX;
    }
}

void hal_uart_int_rx_enable(U32 base, U32 enable)
{
    struct dw_uart_reg* regs = (struct dw_uart_reg*)base;

    if (enable)
    {
        regs->IER.IER |= UART_IER_RX;
    }
    else
    {
        regs->IER.IER &= ~UART_IER_RX;
    }
}

U32 hal_uart_int_rx_check(U32 base, U8* data)
{
    struct dw_uart_reg* regs = (struct dw_uart_reg*)base;

    if ((regs->IER.IER & UART_IER_RX) && (regs->LSR & UART_LSR_RXNE))
    {
        *data = regs->DR.DR;

        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

U32 hal_uart_int_tx_check(U32 base, U8 data)
{
    struct dw_uart_reg* regs = (struct dw_uart_reg*)base;

    if ((regs->IER.IER & UART_IER_TX) && (regs->LSR & UART_LSR_THRE))
    {
        regs->DR.DR = data;

        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

U32 hal_uart_send_char(U32 base, U8 data)
{
    struct dw_uart_reg* regs = (struct dw_uart_reg*)base;

    if (regs->LSR & UART_LSR_THRE)
    {
        regs->DR.DR = data;

        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

U32 hal_uart_recv_char(U32 base, U8* data)
{
    struct dw_uart_reg* regs = (struct dw_uart_reg*)base;

    if (regs->LSR & UART_LSR_RXNE)
    {
        *data = regs->DR.DR;

        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

/*
 * Page[216]: Program Example
 */
void hal_uart_baudrate_set(U32 base, U32 clock, U32 baudrate)
{
    struct dw_uart_reg* regs = (struct dw_uart_reg*)base;
	U32 divisor;

	divisor = clock / baudrate;

    // 1. Enter DLAB mode, set baudrate
    regs->LCR |= UART_LCR_DLAB;

    regs->DR.DLL = divisor & 0xFF;
    regs->IER.DLH = (divisor >> 8) & 0xFF;

	// If input clock is slow, should use DLF
    //regs->DLF = UART_DIVISOR_FRA & 0xFF;

    // 2. Exit DLAB mode
    regs->LCR &= ~UART_LCR_DLAB;
}

void hal_uart_format_set(U32 base)
{
    struct dw_uart_reg* regs = (struct dw_uart_reg*)base;

    regs->LCR = UART_LCR_NO_PARITY | UART_LCR_1BIT_STOP | UART_LCR_8BIT_DATA;
}

void hal_uart_fifo_enable(U32 base)
{
    struct dw_uart_reg* regs = (struct dw_uart_reg*)base;

    regs->FCR.FCR |= UART_FCR_ENABLE;
}

/*_____________________________________________________________________________
||
|| TestCases for DW UART
||_____________________________________________________________________________
*/

#ifdef DW_UART_TEST

static U32 uart_fifo_test(U32 base)
{
	int ret;
	unsigned char c;
	struct dw_uart_reg* regs = (struct dw_uart_reg*)base;
	
	regs->FAR |= UART_FAR_EN;

	// Write to TX FIFO
    regs->DR.DR = 0xaa;
    regs->DR.DR = 0x55;

	// Access the TX FIFO
	c = regs->TFR;
	if (0xaa != c)
	{
		ret = -1;
		goto END;
	}
	
	c = regs->TFR;
	if (0x55 != c)
	{
		ret = -1;
		goto END;
	}

	ret = 0;

END:
	regs->FAR = 0;
	return ret;
}

#endif
